'''

    SHIK.devices._abstract contains base class for devices and
    control:
        SHIK.devices._abstract.Device
        SHIK.devices._abstract.PushControl

'''

from SHIK.keygens._abstract import KeyGen
from SHIK.controlmap import ControlMap

# FIXME: the pushcontrol should be singletons based
# on they class and value. This would save instanciation,
# memory, comparsion and garbage collecting time.
class PushControl(object):
    '''
    Subclass this to create new control types.
    Instanciate a control with a given value to distinguish
    control of the same type.
    '''
    def __init__(self, value):
        self._value = value
    
    @property
    def value(self): return self._value
    
    def __repr__(self):
        return __name__+'.'+self.__class__.__name__+'('+repr(self.value)+')'
    
    def __str__(self):
        return str(self.value)
    
    def __eq__(self, other):
        return type(self) == type(other) and self.value == other.value

    def __hash__(self):
        return self.value.__hash__()


def listenable(func):
    '''
    Turns a method into a listenable one.
    A call to every listener registered with the
    add_listener(device, func_name) method will be triggered when the
    decorated method returns.
    '''
    def wrapper(self, *args,**kwargs):
        if self.disabled():
            print "DISABLED DEVICE, Skipping", self, func, args, kwargs
            return
        ret = func(self, *args,**kwargs)
        [ l(self, func.__name__) for l in self._listeners ]
        return ret
    return wrapper

class Device(object):
    def __init__(self):
        '''
        You must subclass this to create a new device
        type.
        
        You will most likely want to subclass PushControl 
        too and make your device generate this new control 
        class.

        The emit method has to be implemented.
        
        A device can be disabled using Device.enable(False) or
        Device.disable()
        Use Device.enabled() to know if the device is enabled.
        The default state is enabled.
        Subclasses may hook up on this by overriding the enable()
        method. While doing so, the subclass MUST call the base
        class implementation.
         
        '''
        self._keygens = []
        self._listeners = []
        self._enabled = True
        
    def enable(self, b=True):
        '''
        If b is False, all listenable methods will
        silently bypass.
        See also self.disable(), self.enabled(), listenable() 
        '''
        self._enabled = b
        return self.enabled
    
    def disable(self):
        return self.enable(False)
    
    def enabled(self):
        return self._enabled
    
    def disabled(self):
        return not self.enabled()
    
    def add_keygen(self, keygen, push_controls):
        '''
        Adds the given key generator to this device so that
        it receives the pressed and released notifications for
        the given PushControl.
        
        The keygen paramter must be an instance of a subclasse
        of SHIK.keygens._abstract.KeyGen()
        
        The push_controls parameters must be an ordered iterable
        of PushControls managed by this Device, and it must have the
        same length has the managed_events() method of the KeyGen
        '''
        assert( isinstance(keygen, KeyGen) )
        assert( keygen not in self._keygens )
        keygen.set_control_map(
            ControlMap(dict(zip(push_controls, keygen.managed_events())))
        )
        self._keygens.append(keygen)
    
    def remove_keygen(self, kg):
        '''
        Removes the given key generator from the ones associated
        to this device.
        
        Raises a ValueError if kg is not associated to this device.
        '''
        self._keygens.remove(kg)
    
    def clear_keygens(self):
        '''
        Removes all the key generators associatied to this device.
        '''
        kgl = tuple(self._keygens)
        [ self.remove_keygen(kg) for kg in kgl ]
    
    def add_listener(self, func):
        '''
        Register a func to call each time the devices
        activates.
        The func argument must be a callable like:
            func(device, func_name)
        
        Subclasses must decorate the new listened functions
        with the listenable() decorator:
            @listenable
            def on_vibrate(self, data):
                ...
        '''
        self._listeners.append(func)
        
    def remove_listener(self, func):
        '''
        Remove a func to call each time the devices
        activates.
        '''
        self._listeners.remove(func)
        
    def emit(self, data):
        '''
        Emits the given data. The data parameter type depends
        on the device and its associated keygens.
        
        Default implementation is to print the representation of
        the data.
        '''
        print self.__class__.__name__, 'default device emit:', repr(data)
        
    def _as_push_control(self, data):
        '''
        Returns a PushControl instance corresponding to the
        given data.
        
        This must be implemented by subclasses.
        '''
        raise NotImplementedError
    
    @listenable
    def on_press(self, data):
        '''
        This converts data to PushControl using _as_push_control(data)
        and sends this control to each associated KeyGen's Pressed()
        method until one reacts.
        Returns True if a keygen has reacted.
        '''
#        print self, "on_press", data
        pc = self._as_push_control(data)
        for kg in self._keygens:
            ret = kg.pressed(pc)
            if ret:
#                print "   heatten by", kg
                return True # Event has been used.
#        print "   unused."
        return False # Event not used.
    
    @listenable
    def on_release(self, data):
        '''
        This converts data to PushControl using _as_push_control(data)
        and sends this control to each associated KeyGen's Released()
        method until one reacts.
        Returns True if a keygen has reacted.
        '''
#        print self, "on_release", data
        pc = self._as_push_control(data)
        for kg in self._keygens:
            to_emit = kg.released(pc)
            if to_emit is not False:
                if to_emit is not None:
#                    print "   generates", to_emit, "from", kg
                    self.emit(to_emit)
                return True # Event has been used.
#        print "   unused."
        return False # Event not used.
